﻿//===============================================================================
// Microsoft Innovation Centre - Western Europe
// Copyright (c) 2008 Microsoft Corporation. All rights reserved.
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY
// OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT
// LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
// FITNESS FOR A PARTICULAR PURPOSE.
//===============================================================================
// The example companies, organizations, products, domain names,
// e-mail addresses, logos, people, places, and events depicted
// herein are fictitious.  No association with any real company,
// organization, product, domain name, email address, logo, person,
// places, or events is intended or should be inferred.
//===============================================================================

using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data.Services.Client;
using System.Threading;
using Microsoft.WVB.Framework.Entities;

namespace Microsoft.WVB.Framework
{
    /// <summary>
    /// A class that defines how to access data
    /// this class does not have a CancelAsync method because underlying classes used for making the call
    /// on the Network do not support cancellation.
    /// </summary>
    public abstract class DataFeedService : IDisposable
    {
        protected class DataFeedServiceState<T>
        {
            public AsyncOperation Operation { get; set; }
            public DataServiceQuery<T> ServiceQuery { get; set; }
            public object UserState { get; set; }
        }

        private DataServiceContext provider;
        private Dictionary<object, AsyncOperation> userStateLifetime = null;
        private SendOrPostCallback onExecuteCompleted;
        private EventHandler<ReadingWritingEntityEventArgs> readingHandler = null;
        private EntityVisitor visitor = null;

        public Uri RootService { get; private set; }

        public event EventHandler<ExecuteQueryCompletedEventArgs> ExecuteQueryCompleted;

        /// <summary>
        /// Public constructor
        /// </summary>
        /// <param name="serviceRoot">the Uri of a RESTfull root service</param>
        /// <param name="concreateVisitor">An EntitiyVisitor derived class that will inspect the Entity
        /// Note that if concreateVisitor is null, the DataFeedService provider switch to ADO.NET 
        /// Data Services schema so developers can use default entities.</param>
        public DataFeedService(Uri serviceRoot, EntityVisitor concreateVisitor)
        {
            this.RootService = serviceRoot;
            visitor = concreateVisitor;
            provider = this.GetDataServiceContext(serviceRoot);
                       
            onExecuteCompleted = new SendOrPostCallback(raiseExecuteCompleted);
            this.userStateLifetime = new Dictionary<object, AsyncOperation>();
        }

        private void raiseExecuteCompleted(object args)
        {
            ExecuteQueryCompletedEventArgs e = args as ExecuteQueryCompletedEventArgs;

            if (args != null)
                OnExecutedCompleted(e);
        }

        /// <summary>
        /// When overriden, this method does not raise the ExecutQueryCompleted event
        /// Developer is invited to raise other events if needed.
        /// </summary>
        /// <param name="e"></param>
        protected virtual void OnExecutedCompleted(ExecuteQueryCompletedEventArgs e)
        {
            if (ExecuteQueryCompleted != null)
                ExecuteQueryCompleted(this, e);
        }

        /// <summary>
        /// Executes a query against a RESTfull datasource or service by specifying a relative uri.
        /// </summary>
        /// <typeparam name="T">the type of the Entity or Collection of Entities being returned</typeparam>
        /// <param name="relativeEntityPath">A relative path (from the serviceRoot Uri) to an Entity or a Collection of Entities</param>
        /// <param name="userState">A unique state for a given Asynchornous call</param>
        public void ExecuteQueryAsync<T>(string relativeEntityPath, object userState)
        {
            DataServiceQuery<T> dataQuery = null;

            // We actually do not care to have object being null because it does not handle more than one request at a time
            // and we do not support CancelAsync
            // if (userState == null)
               // throw new ArgumentNullException("userState", Strings.DataService_ArgumentNotNull);

            AsyncOperation op = AsyncOperationManager.CreateOperation(userState);

            if (userState != null)
            {
                lock (((ICollection)userStateLifetime).SyncRoot)
                {
                    if (userStateLifetime.ContainsKey(userState))
                        throw new ArgumentException(Strings.DataService_UserStateMustBeUnique);

                    userStateLifetime.Add(userState, op);
                }
            }

            dataQuery = provider.CreateQuery<T>(relativeEntityPath);
            DataFeedServiceState<T> state = new DataFeedServiceState<T>() { Operation = op, ServiceQuery = dataQuery, UserState = userState };
            dataQuery.BeginExecute(new AsyncCallback(executedComplete<T>), state);
        }

        /// <summary>
        /// Executes a query against a RESTfull datasource or service by specifying a full uri.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="entityPath"></param>
        /// <param name="userState"></param>
        public void ExecuteQueryAsync<T>(Uri entityPath, object userState)
        {
            if (userState == null)
                throw new ArgumentNullException("userState", Strings.DataService_ArgumentNotNull);

            AsyncOperation op = AsyncOperationManager.CreateOperation(userState);

            if (userState != null)
            {
                lock (((ICollection)userStateLifetime).SyncRoot)
                {
                    if (userStateLifetime.ContainsKey(userState))
                        throw new ArgumentException(Strings.DataService_UserStateMustBeUnique);

                    userStateLifetime.Add(userState, op);
                }
            }

            DataFeedServiceState<T> state = new DataFeedServiceState<T>() { Operation = op, ServiceQuery = null, UserState = userState };
            provider.BeginExecute<T>(entityPath, new AsyncCallback(executedComplete<T>), state);
        }

        /// <summary>
        /// The method used for handling the AsyncCallback
        /// </summary>
        /// <typeparam name="T">The type of the Entitiy or Collection of Entities being returned</typeparam>
        /// <param name="asyncResult">The IASyncResult object</param>
        private void executedComplete<T>(IAsyncResult asyncResult)
        {
            IEnumerable<T> result = null;
            Exception operationException = null;

            DataFeedServiceState<T> state = (DataFeedServiceState<T>)asyncResult.AsyncState;
            AsyncOperation op = state.Operation;

            if (state.UserState != null)
            {
                if (userStateLifetime.ContainsKey(state.UserState))
                {
                    userStateLifetime.Remove(state.UserState);
                }
            }

            if (asyncResult.IsCompleted)
            {
                try
                {
                    if (state.ServiceQuery != null)
                        result = state.ServiceQuery.EndExecute(asyncResult);
                    else
                        result = provider.EndExecute<T>(asyncResult);
                }
                catch (Exception ex)
                {
                    operationException = ex;
                }
            }

            ExecuteQueryCompletedEventArgs arg = new ExecuteQueryCompletedEventArgs(result, typeof(T), operationException, false, state.UserState);
            op.PostOperationCompleted(raiseExecuteCompleted, arg);
        }

        /// <summary>
        /// When overriden, gives access to the underlying object responsible for the queries.
        /// </summary>
        /// <param name="uri">a Uri object which is the root of the RESTfull service.</param>
        /// <returns>the underlying DataServiceContext used by the DataService object.</returns>
        protected virtual DataServiceContext GetDataServiceContext(Uri uri)
        {
            DataServiceContext svcCtx = null;
            svcCtx = new DataServiceContext(uri);
            
            if (visitor != null)  
            {
                svcCtx.DataNamespace = "http://dpewehq.microsoft.com/WVB";

                if (readingHandler == null)
                {
                    readingHandler = delegate(object sender, ReadingWritingEntityEventArgs e)
                    {
                        BaseEntity tempE = e.Entity as BaseEntity;
                        if ((tempE != null) && (visitor != null))
                        {
                            visitor.VisitEntity(e.Entity as BaseEntity, e.Data);
                        }
                    };
                }
                svcCtx.ReadingEntity += readingHandler;
            }

            return svcCtx;
        }

        #region IDisposable Members

        public virtual void Dispose()
        {
            if ((provider != null) && (readingHandler != null))
            {
                provider.ReadingEntity -= readingHandler;
            }

            provider = null;
        }

        #endregion
    }
}
